package com.sound.ampache;

/* Copyright (c) 2010 Brian Kirkman <kirkman.brian@gmail.com>
 *
 * +------------------------------------------------------------------------+
 * | This program is free software; you can redistribute it and/or          |
 * | modify it under the terms of the GNU General Public License            |
 * | as published by the Free Software Foundation; either version 3         |
 * | of the License, or (at your option) any later version.                 |
 * |                                                                        |
 * | This program is distributed in the hope that it will be useful,        |
 * | but WITHOUT ANY WARRANTY; without even the implied warranty of         |
 * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          |
 * | GNU General Public License for more details.                           |
 * |                                                                        |
 * | You should have received a copy of the GNU General Public License      |
 * | along with this program; if not, write to the Free Software            |
 * | Foundation, Inc., 59 Temple Place - Suite 330,                         |
 * | Boston, MA  02111-1307, USA.                                           |
 * +------------------------------------------------------------------------+
 *
 * Special thanks to the following: 
 * Kevin Purdy, author of Amdroid, for much of the code used in this project
 * 
 * Jorge Ramon, author of MiamiCoder blog, for many great BlackBerry examples
 * published on his blog.
 * 
 * 
 */

import java.io.*;

import net.rim.device.api.crypto.SHA256Digest;

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import com.sound.ampache.objects.*;

import java.lang.Integer;
import java.lang.Long;
import java.util.*;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;

public class ampacheCommunicator implements HTTPTransport
{

	public String authToken = "";
	private int artists;
	private int albums;
	private int songs;
	private int playlists;
	private int tags;
    private String update;
    private String lastErr;

    private String storeUrl;
    private String storeUser;
    private String storePassword;
    private String connTypeString;
    private int maxLimit = 200;  //set the number of items to download from Ampache at once, then loop through
    
    private HTTPTransportListener listener = null;
    
    ampacheCommunicator() {
    	getStoreData();
    }
    
    public void getStoreData() { 
        storeUrl = DataStore.getAppServerUrl();
        storeUser = DataStore.getUserName();
        storePassword = DataStore.getPassword();
    }
    
    
    public synchronized void ping() {
        Thread t = new Thread(new Runnable(){
            public void run(){
                try {

			        InputStream is = null;
				    HttpConnection connection = null; 
				    connTypeString = TransportUtils.getConnectionString();
			    				        
			        /* request server auth */
			        ampacheAuthParser hand = new ampacheAuthParser();
			        
			        //Build the url
			        String url = storeUrl + "/server/xml.server.php?action=ping&auth="
			        	+ authToken + connTypeString;
			        
			        //Create connection
			        connection = (HttpConnection)Connector.open(url);
			        
			        // get the response (input)
			        is = connection.openInputStream();
			        
			        //Parse input stream
			        try {
			        	XMLReader reader = XMLReaderFactory.createXMLReader();
			        	reader.setContentHandler(hand);
			            reader.parse(new InputSource(is));
			        } catch (Exception exc) {
			            exc.printStackTrace();
			        }
			
			        if (hand.errorCode != 0) {
			            lastErr = hand.error;
			        }
                }
                catch(IOException ioe) {
                    ioe.printStackTrace();
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        });
        t.start(); 
    }
    
    
    public class perform_auth_request extends Thread {
    	public void run(){
                try {
			        /* Get the current time, and convert it to a string */
			        String time = Long.toString((new Date()).getTime() / 1000);
			  
			        /* first hash the password */
			        String pwHash = generateSHA256Hash(storePassword);
			        
			        /* now append the time */
			        String preHash = time + pwHash;
			        
			        /* then hash the timestamp in */
			        String hash = generateSHA256Hash(preHash);
       	
			        InputStream is = null;
				    HttpConnection connection = null; 
				    connTypeString = TransportUtils.getConnectionString();
				    authToken = "";
			    				        
			        /* request server auth */
			        ampacheAuthParser hand = new ampacheAuthParser();
			        
			        //Build the url
			        String url = storeUrl + "/server/xml.server.php?action=handshake&auth="
			        	+ hash + "&timestamp=" + time + "&version=350001&user=" + storeUser + connTypeString;
			        
			        //Create connection
			        connection = (HttpConnection)Connector.open(url);
			        
			        // get the response (input)
			        is = connection.openInputStream();
			        
			        //Parse input stream
			        try {
			        	XMLReader reader = XMLReaderFactory.createXMLReader();
			        	reader.setContentHandler(hand);
			            reader.parse(new InputSource(is));
			        } catch (Exception exc) {
			            exc.printStackTrace();
			        }
			
			        if (hand.errorCode != 0) {
			            lastErr = hand.error;
			        }
			
			        authToken = hand.token;  
			        artists = hand.artists;
			        albums = hand.albums;
			        songs = hand.songs;
			        playlists = hand.playlists;
			        tags = hand.tags;
			        update = hand.update; 
			        
                } catch(IOException ioe) {
                    ioe.printStackTrace();
                } catch(Exception e) {
                    e.printStackTrace();
                } 
            }
    
   
    private String generateSHA256Hash(String username) {
		byte[] bytes = username.getBytes();
		SHA256Digest digest = new SHA256Digest();
		digest.update(bytes, 0, bytes.length);
		int length = digest.getDigestLength();
		byte[] md5 = new byte[length];
		digest.getDigest(md5, 0, true);
		return convertToHex(md5);
    }
    
    
	private String convertToHex(byte[] data) {
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < data.length; i++) {
			int halfbyte = (data[i] >>> 4) & 0x0F;
			int two_halfs = 0;
			do {
				if ((0 <= halfbyte) && (halfbyte <= 9))
					buf.append((char) ('0' + halfbyte));
				else
					buf.append((char) ('a' + (halfbyte - 10)));
					halfbyte = data[i] & 0x0F;
			} while(two_halfs++ < 1);
		}
		return buf.toString();
	}
	
    }
    
    
	public synchronized void send(final String method, final String filter) {
        // we want to thread this out so it doesn't block the calling class
        // connections should be made in the background
        Thread t = new Thread(new Runnable(){
            public void run(){
                try {
			    	//Define dataHandler
			    	dataHandler hand; 
			        // setup methods to append to request url 
			        String append = "action=" + method + "&filter=" + filter + "&auth=" + authToken;
        
			        if (method.equals("artists")) {
			            hand = new ampacheArtistParser();
			        } else if (method.equals("artist_albums")) {
			            hand = new ampacheAlbumParser();
			        } else if (method.equals("artist_songs")) {
			            hand = new ampacheSongParser();
			        } else if (method.equals("album_songs")) {
			            hand = new ampacheSongParser();        
			        } else if (method.equals("playlist_songs")) {
			            hand = new ampacheSongParser();
			        } else if (method.equals("tag_artists")) {
			            hand = new ampacheArtistParser();
			        } else if (method.equals("tag_albums")) {
			            hand = new ampacheAlbumParser();
			        } else if (method.equals("tag_songs")) {
			            hand = new ampacheSongParser();
			        } else if (method.equals("albums")) {
			            hand = new ampacheAlbumParser();
			        } else if (method.equals("playlists")) {
			            hand = new ampachePlaylistParser();
			        } else if (method.equals("songs")) {
			            hand = new ampacheSongParser();
			        } else if (method.equals("tags")) {
			            hand = new ampacheTagParser();
			        } else if (method.equals("search_songs")) {
			            hand = new ampacheSongParser();
			        } else {
			            return;
			        }
        
			        //Create a loop that incrementally downloads items in small chunks
			        //of maxLimit items.  This should theoretically be easier on
			        //the streaming data connection, especially for large catalogs
			        int vectorSize = 0;
			        int i=0;
			        while (true) {
			            String offset = "&offset=" + maxLimit*i + "&limit=" + maxLimit;
			            
			            //Build the url
			            String url = storeUrl + "/server/xml.server.php?" + append + offset + connTypeString; 
			            
			            //create the connection
	                    HttpConnection http = (HttpConnection)Connector.open(url);
			            
	                    //get the response (input)
	                    InputStream in = http.openInputStream();
	                    	                    
	                    //read in all the message characters received from the server
	                    /* all done loading data, now to parse */
	                    try {
	                    	XMLReader reader = XMLReaderFactory.createXMLReader();
	                    	reader.setContentHandler(hand);
	                        reader.parse(new InputSource(in));
	                    } catch (Exception exc) {
	                        exc.printStackTrace();
	                    }
	                    
	                    // close the input stream
	                    in.close();  
	                 
	                    //close the connections
	                    http.close();
	                    
			            //exit the while loop if the data Vector has not changed in size
			            if (vectorSize == hand.data.size()) break;
			            //exit the while loop for a single iteration less than the maxLimit
			            if (hand.data.size() < maxLimit) break;
			            
			            //keep track of the vector size in order to exit the loop
			            //on no change in size on the next iteration
			            vectorSize = hand.data.size(); 
			            
			            //Increment the offset in the loop
			            i++;
			        }
			        processResponse(hand.data);
                }
                catch(IOException ioe) {
                    ioe.printStackTrace();
                }
                catch(Exception e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();  
    }
		
	
    public void setHTTPTransportListener(HTTPTransportListener t){
        listener = t;
    }
    
    
    public void removeHTTPTransportListener(){
        listener = null;
    }
    
    
    private void processResponse(Vector data){
        if (listener != null){
            listener.processResponse(data);
        }
    }
 
    
    /* The following are all the handlers / parsers */
    /* ******************************************** */
    
    private class dataHandler extends DefaultHandler {
        public Vector data = new Vector();
        public String error = null;
        public int errorCode = 0;
        protected StringBuffer contents = new StringBuffer();

        public void startDocument() throws SAXException {
            
        }
        
        public void endDocument() throws SAXException {

        }

        public void characters( char[] ch, int start, int length )throws SAXException {
            contents.append( ch, start, length );
        }

        public void startElement( String namespaceURI,
                                  String localName,
                                  String qName,
                                  Attributes attr) throws SAXException {

            if (localName.equals("error"))
                errorCode = Integer.parseInt(attr.getValue("code"));
            contents.setLength(0);
        }

        public void endElement( String namespaceURI,
                                String localName,
                                String qName) throws SAXException {
            if (localName.equals("error")) {
                error = contents.toString();
            }
        }
        
    }

    
    private class ampacheAuthParser extends dataHandler {
        public String token = "";
        public int artists = 0;
        public int albums = 0;
        public int songs = 0;
        public int playlists = 0;
        public int tags = 0;
        public String update = "";

        public void endElement( String namespaceURI,
                                String localName,
                                String qName) throws SAXException {

            super.endElement(namespaceURI, localName, qName);

            if (localName.equals("auth")) {
                token = contents.toString();
            }
            if (localName.equals("artists")) {
                artists = Integer.parseInt(contents.toString());
            }
            if (localName.equals("albums")) {
                albums = Integer.parseInt(contents.toString());
            }
            if (localName.equals("songs")) {
                songs = Integer.parseInt(contents.toString());
            }
            if (localName.equals("playlists")) {
                playlists = Integer.parseInt(contents.toString());
            }
            if (localName.equals("tags")) {
                tags = Integer.parseInt(contents.toString());
            }
            if (localName.equals("add")) {
                update = contents.toString();
            }
        }
    }


    private class ampacheArtistParser extends dataHandler {
        private Artist current;
        
        public void startElement( String namespaceURI,
                                  String localName,
                                  String qName,
                                  Attributes attr) throws SAXException {
            
            super.startElement(namespaceURI, localName, qName, attr);

            if (localName.equals("artist")) {
                current = new Artist();
                current.id = attr.getValue("id");
            }
        }
        
        public void endElement( String namespaceURI,
                                String localName,
                                String qName) throws SAXException {
            
            super.endElement(namespaceURI, localName, qName);

            if (localName.equals("name")) {
                current.name = contents.toString();
            }

            if (localName.equals("albums")) {
                current.albums = contents.toString() + " albums";
            }
            
            if (localName.equals("songs")) {
                current.songs = contents.toString() + " songs";
            }

            if (localName.equals("artist")) {
                data.addElement(current);
            }

        }
    }
    
    
    private class ampacheAlbumParser extends dataHandler {
        private Album current;
        
        public void startElement( String namespaceURI,
                                  String localName,
                                  String qName,
                                  Attributes attr) throws SAXException {
            
            super.startElement(namespaceURI, localName, qName, attr);

            if (localName.equals("album")) {
                current = new Album();
                current.id = attr.getValue("id");
            }
        }
        
        public void endElement( String namespaceURI,
                                String localName,
                                String qName) throws SAXException {
            
            super.endElement(namespaceURI, localName, qName);

            if (localName.equals("name")) {
                current.name = contents.toString();
            }
            
            if (localName.equals("artist")) {
                current.artist = contents.toString();
            }

            if (localName.equals("tracks")) {
                current.tracks = contents.toString();
            }
            
            if (localName.equals("art")) {
                current.art = contents.toString();
            }

            if (localName.equals("album")) {
                data.addElement(current);
            }
        }
    }
    
    
    private class ampacheTagParser extends dataHandler {
        private Tag current;
        
        public void startElement( String namespaceURI,
                                  String localName,
                                  String qName,
                                  Attributes attr) throws SAXException {
            
            super.startElement(namespaceURI, localName, qName, attr);

            if (localName.equals("tag")) {
                current = new Tag();
                current.id = attr.getValue("id");
            }
        }
        
        public void endElement( String namespaceURI,
                                String localName,
                                String qName) throws SAXException {
            
            super.endElement(namespaceURI, localName, qName);

            if (localName.equals("name")) {
                current.name = contents.toString();
            }
	    if (localName.equals("albums")) {
		current.albums = contents.toString();
	    }
	    if (localName.equals("artists")){
		current.artists = contents.toString();
	    }
            if (localName.equals("tag")) {
                data.addElement(current);
            }
        }
    }
    
    
    private class ampachePlaylistParser extends dataHandler {
        private Playlist current;
        
        public void startElement( String namespaceURI,
                                  String localName,
                                  String qName,
                                  Attributes attr) throws SAXException {
            
            super.startElement(namespaceURI, localName, qName, attr);

            if (localName.equals("playlist")) {
                current = new Playlist();
                current.id = attr.getValue("id");
            }
        }
        
        public void endElement( String namespaceURI,
                                String localName,
                                String qName) throws SAXException {
            
            super.endElement(namespaceURI, localName, qName);

            if (localName.equals("name")) {
                current.name = contents.toString();
            }
            
            if (localName.equals("owner")) {
                current.owner = contents.toString();
            }

            if (localName.equals("items")) {
                current.count = contents.toString();
            }
            
            if (localName.equals("playlist")) {
                data.addElement(current);
            }
        }
    }
    
    
    private class ampacheSongParser extends dataHandler {
        private Song current;
        
        public void startElement( String namespaceURI,
                                  String localName,
                                  String qName,
                                  Attributes attr) throws SAXException {
            
            super.startElement(namespaceURI, localName, qName, attr);

            if (localName.equals("song")) {
                current = new Song();
                current.id = attr.getValue("id");
            }
        }
        
        public void endElement( String namespaceURI,
                                String localName,
                                String qName) throws SAXException {
            
            super.endElement(namespaceURI, localName, qName);

            if (localName.equals("song")) {
                data.addElement(current);
            }
            
            if (localName.equals("title")) {
                current.name = contents.toString();
            }
            
            if (localName.equals("artist")) {
                current.artist = contents.toString();
            }
            
            if (localName.equals("art")) {
                current.art = contents.toString();
            }
            
            if (localName.equals("url")) {
                current.url = contents.toString();
            }

            if (localName.equals("album")) {
                current.album = contents.toString();
            }
            
            if (localName.equals("time")) {
                current.time = contents.toString();
            }

            if (localName.equals("genre")) {
                current.genre = contents.toString();
            }
        }
    }
    
    
    public int getArtists() {
    	return artists;
    }
    
    public int getAlbums() {
    	return albums;
    }
    
    public int getSongs() {
    	return songs;
    }
    
    public int getPlaylists() {
    	return playlists;
    }
    
    public int getTags() {
    	return tags;
    }
    
    public String getLastErr() {
    	return lastErr;
    }

}

